package com.sunday.framework.transaction.autoconfiguration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 数据库事务处理 - AOP 切面方式配置事务
 * <p>
 * 默认实现 :
 * 1. 数据库事务
 * <p>
 * https://blog.csdn.net/xiaoxiaohacker/article/details/89206947
 * https://blog.csdn.net/sinat_36710456/article/details/83821037
 * <p>
 * mybatis log
 * <p>
 * 一.  不存在aop事物
 * <p>
 * Creating a new SqlSession
 * SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@206543bf] was not registered for synchronization because synchronization is not active
 * JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1efc1032] will not be managed by Spring
 * ==>  Preparing: INSERT INTO pd_blacklist_group ( id, name, create_time ) VALUES ( ?, ?, ? )
 * ==> Parameters: 1461860591151874141(Long), 2333(String), 2021-11-20T08:54:59.644(LocalDateTime)
 * <==    Updates: 1
 * Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@206543bf]
 * <p>
 * 二.  存在aop事物
 * <p>
 * Creating a new SqlSession
 * Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1ae7a573]
 * JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@12970008] will be managed by Spring
 * ==>  Preparing: INSERT INTO pd_blacklist_group ( id, name, create_time ) VALUES ( ?, ?, ? )
 * ==> Parameters: 1461871934508630091(Long), 2333(String), 2021-11-20T09:40:04.111(LocalDateTime)
 * <==    Updates: 1
 * Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1ae7a573]
 * Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1ae7a573]
 * Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1ae7a573]
 * Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1ae7a573]
 * <p>
 * 三. 事物回滚
 * <p>
 * Creating a new SqlSession
 * Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32a5e91e]
 * JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@456800de] will be managed by Spring
 * ==>  Preparing: INSERT INTO pd_blacklist_group ( id, name, create_time ) VALUES ( ?, ?, ? )
 * ==> Parameters: 1461882988949143581(Long), 2333(String), 2021-11-20T10:23:59.695(LocalDateTime)
 * <==    Updates: 1
 * Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32a5e91e]
 * Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32a5e91e]
 * Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32a5e91e]
 */
@Slf4j
@Configuration
@Import(DefaultMybatisPlusConfig.class)
@ConditionalOnProperty(prefix = "sunday.framework.transaction", value = "execution")
public class TransactionAdvisorAutoConfiguration {

    @Bean
    public Advisor transactionPointcutAdvisor(@Value("${sunday.framework.transaction.execution}") String execution, TransactionManager transactionManager) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(execution);
        return new DefaultPointcutAdvisor(pointcut, txAdvice(transactionManager));
    }

    private TransactionInterceptor txAdvice(TransactionManager transactionManager) {

        /*当前存在事务就使用当前事务，当前不存在事务就创建一个新的事务*/
        RuleBasedTransactionAttribute txAttr_REQUIRED = new RuleBasedTransactionAttribute();
        txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        txAttr_REQUIRED.setRollbackRules(Arrays.asList(new RollbackRuleAttribute(Exception.class), new RollbackRuleAttribute(Throwable.class)));

        /*只读事务，不做更新操作*/
        RuleBasedTransactionAttribute txAttr_REQUIRED_READONLY = new RuleBasedTransactionAttribute();
        txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        txAttr_REQUIRED_READONLY.setReadOnly(true);

        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        Map<String, TransactionAttribute> txMap = new HashMap<>();

        /********************************    Service/DAO 层方法命名规约   *****************************/

        /**
         * 写操作
         *
         * 1）	插入的方法用 save/insert/batch 做前缀。
         * 2）	删除的方法用 remove/delete 做前缀。
         * 3）	修改的方法用 update 做前缀。
         * 4）  执行脚本用 execute 做前缀
         */
        txMap.put("save*", txAttr_REQUIRED);
        txMap.put("insert*", txAttr_REQUIRED);
        txMap.put("batch*", txAttr_REQUIRED);

        txMap.put("remove*", txAttr_REQUIRED);
        txMap.put("delete*", txAttr_REQUIRED);

        txMap.put("update*", txAttr_REQUIRED);

        txMap.put("execute*", txAttr_REQUIRED);
        /**
         * 读操作
         *
         * 1）	获取单个对象的方法用 get做前缀。
         * 2）	获取多个对象的方法用 list 做前缀，复数结尾，如：listObjects。
         * 3）	获取统计值的方法用 count 做前缀。
         * 4）	获取分页值的方法用 page 做前缀。
         */
        txMap.put("get*", txAttr_REQUIRED_READONLY);

        txMap.put("list*", txAttr_REQUIRED_READONLY);

        txMap.put("count*", txAttr_REQUIRED_READONLY);

        txMap.put("page*", txAttr_REQUIRED_READONLY);

        txMap.put("*", txAttr_REQUIRED_READONLY);

        source.setNameMap(txMap);

        return new TransactionInterceptor(transactionManager, source);
    }

}